#ifndef __MP_MQTT_PACKET_H__
#define __MP_MQTT_PACKET_H__

#include "net_packet.h"

namespace Gazella {

enum MQTTControlPacketType {
    MQTT_CONTROL_CONNECT     = 1u, //Client --> Server
    MQTT_CONTROL_CONNACK     = 2u, //Client <-- Server
    MQTT_CONTROL_PUBLISH     = 3u, //Client <-> Server
    MQTT_CONTROL_PUBACK      = 4u, //Client <-> Server
    MQTT_CONTROL_PUBREC      = 5u, //Client <-> Server
    MQTT_CONTROL_PUBREL      = 6u, //Client <-> Server
    MQTT_CONTROL_PUBCOMP     = 7u, //Client <-> Server
    MQTT_CONTROL_SUBSCRIBE   = 8u, //Client --> Server
    MQTT_CONTROL_SUBACK      = 9u, //Client <-- Server
    MQTT_CONTROL_UNSUBSCRIBE = 10u,//Client --> Server
    MQTT_CONTROL_UNSUBACK    = 11u,//Client <-- Server
    MQTT_CONTROL_PINGREQ     = 12u,//Client --> Server
    MQTT_CONTROL_PINGRESP    = 13u,//Client <-- Server
    MQTT_CONTROL_DISCONNECT  = 14u,//Client --> Server
};

enum MQTTConnectFlags {
    MQTT_CONNECT_RESERVED      = 1u,
    MQTT_CONNECT_CLEAN_SESSION = 2u,
    MQTT_CONNECT_WILL_FLAG     = 4u,
    MQTT_CONNECT_WILL_QOS_0    = (0u & 0x03) << 3,
    MQTT_CONNECT_WILL_QOS_1    = (1u & 0x03) << 3,
    MQTT_CONNECT_WILL_QOS_2    = (2u & 0x03) << 3,
    MQTT_CONNECT_WILL_RETAIN   = 32u, 
    MQTT_CONNECT_PASSWORD      = 64u, 
    MQTT_CONNECT_USER_NAME     = 128u                                                                                                  
};

enum MQTTConnackReturnCode {
    MQTT_CONNACK_ACCEPTED                          = 0u,
    MQTT_CONNACK_REFUSED_PROTOCOL_VERSION          = 1u,
    MQTT_CONNACK_REFUSED_IDENTIFIER_REJECTED       = 2u,
    MQTT_CONNACK_REFUSED_SERVER_UNAVAILABLE        = 3u,
    MQTT_CONNACK_REFUSED_BAD_USER_NAME_OR_PASSWORD = 4u,
    MQTT_CONNACK_REFUSED_NOT_AUTHORIZED            = 5u 
};

enum MQTTPublishFlags {                                                                                                            
    MQTT_PUBLISH_DUP      = 8u,
    MQTT_PUBLISH_QOS_0    = ((0u << 1) & 0x06),
    MQTT_PUBLISH_QOS_1    = ((1u << 1) & 0x06),
    MQTT_PUBLISH_QOS_2    = ((2u << 1) & 0x06),
    MQTT_PUBLISH_QOS_MASK = ((3u << 1) & 0x06),
    MQTT_PUBLISH_RETAIN   = 0x01
};

enum MQTTSubackReturnCodes {
    MQTT_SUBACK_SUCCESS_MAX_QOS_0 = 0u,
    MQTT_SUBACK_SUCCESS_MAX_QOS_1 = 1u,
    MQTT_SUBACK_SUCCESS_MAX_QOS_2 = 2u,
    MQTT_SUBACK_FAILURE           = 128u
};

enum MqttPacketErrorEnum {
    MQTT_PACKET_FMT_REROR    = -1,
    MQTT_PACKET_NOT_COMPLETE = -2,
};

enum MqttPublishQosStep{
    MQTT_PUBLISH_QOS_STEP_NONE = 0u,
    MQTT_PUBLISH_QOS_STEP_ACK  = 1u, //for qos1
    MQTT_PUBLISH_QOS_STEP_REC  = 2u, //for qos2
    MQTT_PUBLISH_QOS_STEP_REL  = 3u, //for qos2
    MQTT_PUBLISH_QOS_STEP_COMP = 4u, //for qos2
};

#define MQTT_PUBLISH_QOS_MAX_TIMEOUT 10

//fixed header
struct MqttPacketFixedHeader
{
    union {
        union {
            struct {
                uint8_t control_flags:4;
                uint8_t control_type:4;
            };

            struct {
                uint8_t retain:1;
                uint8_t qos:2;
                uint8_t dup:1;
            } publish_flags;
        };

        uint8_t byte;
    } control;

    uint32_t remaining_length;
};

//packet define
struct MqttPacketLengthField{
    uint8_t msb;
    uint8_t lsb;

    uint16_t length() {
        return (msb << 8) | lsb;
    }

    void length(uint16_t length) {
        msb = (length >> 8) & 0xFF;
        lsb = (length >> 0) & 0xFF;
    }

    bool operator==(MqttPacketLengthField& l) {
        return length() == l.length();
    }

    bool operator==(uint16_t l) {
        return length() == l;
    }
};

struct MqttPacketDataField {
    MqttPacketLengthField length;
    std::string           data;
};

//connect
//direction C=>S
struct MqttPacketConnect {
    //variable header
    MqttPacketDataField   protocol;

    uint8_t               level = 0x04; //v3

    union {
        struct {
            uint8_t reserved:1;
            uint8_t clean_session:1;
            uint8_t will_flag:1;
            uint8_t will_qos:2;
            uint8_t will_retain:1;
            uint8_t password_flag:1;
            uint8_t username_flag:1;
        };

        uint8_t flags;
    } connect_flags;

    MqttPacketLengthField keep_alive;

    //payload(oneof)
    std::string client_id;
    std::string will_topic;
    std::string will_message;
    std::string username;
    std::string password;
};

//connect ack
//direction C<=S
struct MqttPacketConnAck {
    //variable header
    uint8_t               session_present_flag;
    MQTTConnackReturnCode return_code;
};

//publish
//direction C<=>S
struct MqttPacketPublish {
    //publish flags
    uint8_t publish_flags:4;

    //variable header
    MqttPacketDataField   topic_name;
    MqttPacketLengthField packet_id; //qos = 1 or 2

    //payload
    std::string message;
};

//for qos1
//direction C<=>S
struct MqttPacketPubAck {
    //variable header
    MqttPacketLengthField packet_id;
};

//for qos2
//direction C<=>S
struct MqttPacketPubRec {
    //variable header
    MqttPacketLengthField packet_id;
};

//for qos2
//direction C<=>S
struct MqttPacketPubRel {
    //variable header
    MqttPacketLengthField packet_id;
};

//for qos2
//direction C<=>S
struct MqttPacketPubComp {
    //variable header
    MqttPacketLengthField packet_id;
};

//subscribe(only support one topic)
//direction C=>S
struct MqttPacketSubscribe {
    //variable header
    MqttPacketLengthField packet_id;
    
    //payload
    MqttPacketDataField   topic_filter;
    uint8_t               request_qos:2;
};

//suback(only support one topic)
//direction C<=S
struct MqttPacketSubAck {
    //variable
    MqttPacketLengthField packet_id;

    //payload
    MQTTSubackReturnCodes return_code;
};

//unsubscribe(only support one topic)
//direction C=>S
struct MqttPacketUnSubscribe {
    //variable
    MqttPacketLengthField packet_id;

    //payload
    MqttPacketDataField   topic_filter;
};

//unsuback
//direction C<=S
struct MqttPacketUnSubAck {
    //variable
    MqttPacketLengthField packet_id;
};

//pingreq
//direction C=>S
struct MqttPacketPingReq {
};

//pingresp
//direction C<=S
struct MqttPacketPingResp {
};

//disconnect
//direction C=>S
struct MqttPacketDisconnect {
};

//mqtt packet wrapper
struct MqttPacketWrapper {
    //fixed header
    MqttPacketFixedHeader fixed_header;

    struct
    {
        MqttPacketConnect     connectPacket;
        MqttPacketConnAck     connackPacket;

        MqttPacketDisconnect  disconnectPacket;

        MqttPacketPublish     publishPacket;
        MqttPacketPubAck      pubackPacket;
        MqttPacketPubRec      pubrecPacket;
        MqttPacketPubRel      pubrelPacket;
        MqttPacketPubComp     pubcompPacket;

        MqttPacketSubscribe   subscribePacket;
        MqttPacketSubAck      subackPacket;

        MqttPacketUnSubscribe unsubscribePacket;
        MqttPacketUnSubAck    unsubackPacket;

        MqttPacketPingReq     pingreqPacket;
        MqttPacketPingResp    pingrespPacket;
    } packet;

    std::string traceid;
    uint32_t    priority;
};

class MqttPacket: public NetPacket {
public:
    MqttPacket();
    ~MqttPacket();

    std::string build() override;
    int         parse(std::string& packet) override;

    void set_mqtt_packet_wrapper(const MqttPacketWrapper& mqttPacketWrapper);
    
    MqttPacketWrapper& get_mqtt_packet_wrapper();

    static std::vector<std::string> parse_topic_filter(std::string topic_filter);
    static bool match_topic_filter(std::string topic_name, std::string topic_filter);

private:
    MqttPacketWrapper m_mqttPacketWrapper;

private:
    //parse
    //connect
    int __parse_connect_packet(std::string packet);
    int __parse_connack_packet(std::string packet);

    //disconnect
    int __parse_disconnect_packet(std::string packet);

    //publish
    int __parse_publish_packet(std::string packet);
    int __parse_puback_packet (std::string packet);
    int __parse_pubrec_packet (std::string packet);
    int __parse_pubrel_packet (std::string packet);
    int __parse_pubcomp_packet(std::string packet);

    //subscribe
    int __parse_subscribe_packet  (std::string packet);
    int __parse_suback_packet     (std::string packet);
    int __parse_unsubscribe_packet(std::string packet);
    int __parse_unsuback_packet   (std::string packet);

    //manager
    int __parse_pingreq_packet (std::string packet);
    int __parse_pingresp_packet(std::string packet);

    //build
    std::string __build_fixed_header();
    //connect
    std::string __build_connect_packet();
    std::string __build_connack_packet();

    //disconnect
    std::string __build_disconnect_packet();

    //publish
    std::string __build_publish_packet();
    std::string __build_puback_packet ();
    std::string __build_pubrec_packet ();
    std::string __build_pubrel_packet ();
    std::string __build_pubcomp_packet();

    //subscribe
    std::string __build_subscribe_packet  ();
    std::string __build_suback_packet     ();
    std::string __build_unsubscribe_packet();
    std::string __build_unsuback_packet   ();

    //manager
    std::string __build_pingreq_packet ();
    std::string __build_pingresp_packet();
};
}

#endif
